//
//  CKConnection.m
//  ConnectionKit
//
//  Created by Matthias Plappert on 13.06.09.
//  Copyright 2009 phapswebsolutions. All rights reserved.
//

#import "CKConnection.h"

@implementation CKConnection

@synthesize timeout, cache, delegate;

- (id)init
{
	if (self = [super init]) {
		_connections = [[NSMutableDictionary alloc] init];
		_data = [[NSMutableDictionary alloc] init];
		_userInfos = [[NSMutableDictionary alloc] init];
		
		// Default values
		timeout = 20.0;
		cache = NSURLRequestUseProtocolCachePolicy;
	}
	return self;
}

- (void)dealloc
{
	delegate = nil;
	
	[_connections release];
	[_data release];
	[_userInfos release];
	
	[super dealloc];
}

#pragma mark -
#pragma mark Connection handling

- (NSError *)load:(NSString *)action synchronous:(BOOL)synchronous userInfo:(NSDictionary *)userInfo
{
	NSURLRequest *request = [self requestForAction:action userInfo:userInfo];
	
	if (synchronous == YES) {
		// Start new connection
		NSError *error = nil;
		NSURLResponse *response = nil;
		NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
		
		if (error != nil) {
			#ifdef DEBUG
				NSLog(@"Connection error: %@", error);
			#endif
			
			return error;
		} else {
			NSError *responseError = [self checkResponse:response forAction:action];
			if (responseError == nil) {
				if (data != nil) {
					NSError *error = [self parse:data action:action userInfo:userInfo];
					return error;
				} else {
					NSError *error = [NSError errorWithDomain:@"de.phapswebsolutions.connectionkit" code:CKConnectionErrorCodeUnknownError userInfo:nil];
					return error;
				}
			} else {
				return responseError;
			}
		}
	} else {
		[self cancelAction:action];
		[_data setObject:[NSMutableData data] forKey:action];
		
		// Start connection
		NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
		[_connections setObject:connection forKey:action];
		if (userInfo != nil) {
			[_userInfos setObject:userInfo forKey:action];
		} else {
			[_userInfos setObject:[NSDictionary dictionary] forKey:action];
		}
		
		// Call delegate method
		if ([self.delegate respondsToSelector:@selector(connection:willReceiveDataForAction:userInfo:)]) {
			[self.delegate connection:self willReceiveDataForAction:action userInfo:userInfo];
		}
		
		return nil;
	}
}

- (NSString *)actionForConnection:(NSURLConnection *)connection
{
	// Returns the action assigned to a conneciton. If connection is not found, return nil
	NSString *action = [[_connections allKeysForObject:connection] objectAtIndex:0];
	return action;
}

- (NSURLConnection *)connectionForAction:(NSString *)action
{
	return [_connections objectForKey:action];
}

- (NSDictionary *)userInfoForConnection:(NSURLConnection *)connection
{
	// Returns the action assigned to a conneciton. If connection is not found, return nil
	NSString *action = [[_connections allKeysForObject:connection] objectAtIndex:0];
	return [_userInfos objectForKey:action];
}

- (void)cancelAction:(NSString *)action
{
	NSURLConnection *connection = [self connectionForAction:action];
	if (connection != nil) {
		[connection cancel];
		[_connections removeObjectForKey:action];
		[_userInfos removeObjectForKey:action];
	}
}

- (void)cancelAllActions
{
	for (NSString *key in _connections) {
		[self cancelAction:key];
	}
}

- (NSError *)checkResponse:(NSURLResponse *)response forAction:(NSString *)action
{
	NSString *MIMEType = [response MIMEType];
	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:response forKey:@"response"];
	NSArray *supportedMIMETypes = [self supportedResponseMIMETypesForAction:action userInfo:userInfo];
	
	if (supportedMIMETypes != nil && ![supportedMIMETypes containsObject:MIMEType]) {
		#ifdef DEBUG
			NSLog(@"Response error: MIMEType is not %@ but %@", supportedMIMETypes, MIMEType);
		#endif
		
		NSError *error = [NSError errorWithDomain:@"de.phapswebsolutions.connectionkit" code:CKConnectionErrorCodeResponseError userInfo:userInfo];
		return error;
	} else {
		return nil;
	}
}

#pragma mark -
#pragma mark NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
	NSString *action = [self actionForConnection:connection];
	NSDictionary *userInfo = [self userInfoForConnection:connection];
	NSError *error = [self checkResponse:response forAction:action];
	
	if (error != nil) {
		[self cancelAction:action];
		
		if ([self.delegate respondsToSelector:@selector(connection:didFailWithError:action:userInfo:)]) {
			[self.delegate connection:self didFailWithError:error action:action userInfo:userInfo];
		}
	}
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
	NSString *action = [self actionForConnection:connection];
	[[_data objectForKey:action] appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
	NSString *action = [self actionForConnection:connection];
	NSDictionary *userInfo = [self userInfoForConnection:connection];
	NSData *data = [_data objectForKey:action];
	
	if (data != nil) {
		NSError *error = [self parse:data action:action userInfo:userInfo];
		
		if (error == nil) {
			// Inform delegate
			if ([self.delegate respondsToSelector:@selector(connection:didReceiveDataForAction:userInfo:)]) {
				[self.delegate connection:self didReceiveDataForAction:action userInfo:userInfo];
			}
		} else {
			// Inform general delegate
			if ([self.delegate respondsToSelector:@selector(connection:didFailWithError:action:userInfo:)]) {
				[self.delegate connection:self didFailWithError:error action:action userInfo:userInfo];
			}
		}
	}
	
	// Release connection and data
	[_connections removeObjectForKey:action];
	[_userInfos removeObjectForKey:action];
	[_data removeObjectForKey:action];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
	#ifdef DEBUG
		NSLog(@"Connection error: %@", error);
	#endif
	
	NSString *action = [self actionForConnection:connection];
	NSDictionary *userInfo = [self userInfoForConnection:connection];
	
	// Release connection and data
	[_connections removeObjectForKey:action];
	[_userInfos removeObjectForKey:action];
	[_data removeObjectForKey:action];
	
	// Inform general delegate
	if ([self.delegate respondsToSelector:@selector(connection:didFailWithError:action:userInfo:)]) {
		[self.delegate connection:self didFailWithError:error action:action userInfo:userInfo];
	}
}

#pragma mark -
#pragma mark Overwrite methods

- (NSArray *)supportedResponseMIMETypesForAction:(NSString *)action userInfo:(NSDictionary *)userInfo;
{
	return nil;
}

- (NSString *)URLForAction:(NSString *)action userInfo:(NSDictionary *)userInfo
{
	return nil;
}

- (NSURLRequest *)requestForAction:(NSString *)action userInfo:(NSDictionary *)userInfo
{
	NSString *URL = [self URLForAction:action userInfo:userInfo];
	NSURLRequest *request;
	
	#ifdef DEBUG
		NSLog(@"request with %fs timeout send to %@", self.timeout, URL);
	#endif
	
	request = [NSURLRequest requestWithURL:[NSURL URLWithString:URL]
							   cachePolicy:self.cache
						   timeoutInterval:self.timeout];
	
	return request;
}

- (NSError *)parse:(NSData *)data action:(NSString *)action userInfo:(NSDictionary *)userInfo
{
	return nil;
}

@end
